32  Kmeans——信用卡用户行为模式聚类

32.1 引言信用卡用户分层

聚类目标:

  • 客户细分: 识别不同消费群体
  • 风险控制: 识别高风险行为
  • 精准营销: 针对性营销策略

32.2 本章学习目标

先修内容:阅读本章前,建议先完成第 章节 10 章(Pandas 数据框基础)、第 章节 14 章(读取外部数据文件)与第 章节 30 章(K-Means 原理与肘部法)。

  1. K-Means 在客户细分场景中的完整应用流程(读取—清洗—聚类—解读);
  2. 删除无关列、前向填充缺失值等聚类前的数据预处理操作;
  3. groupby 计算各簇特征均值,为每个客户群刻画行为画像;
  4. 用轮廓系数评估聚类质量并解读其取值区间;
  5. 在教学平台完成信用卡用户行为模式聚类任务。

32.3 数据准备

列表 32.1: 数据准备与预处理
# 注:09_CC GENERAL.csv数据文件本地没有,但平台已经内置

# =============================================================================
# 题目:K-Means信用卡用户行为模式聚类
# =============================================================================
# 本代码使用K-Means算法对信用卡用户进行聚类分析。
# 聚类是无监督学习方法,将相似的用户归为一组,发现数据内在结构。
# 在金融场景中用于客户细分、风险识别、精准营销等。

# ==================== 导入必要的库 ====================
import pandas as pd  # 数据处理库
import numpy as np  # 数值计算库
from sklearn.cluster import KMeans  # K-Means聚类算法
from sklearn.metrics import silhouette_score  # 轮廓系数评估指标
import matplotlib.pyplot as plt  # 绘图库
import seaborn as sns  # 统计绘图库

# ==================== 读取数据 ====================
# 读取信用卡用户数据,包含用户ID和各种消费行为特征
data = pd.read_csv('09_CC GENERAL.csv')  # 从CSV文件加载数据

# ==================== 数据探索 ====================
print('数据形状:', data.shape)  # 打印数据维度(样本数,特征数)
print('\n数据预览:')
print(data.head())  # 显示前5行数据,了解数据结构

# ==================== 数据清洗 ====================
# 删除客户ID列,因为聚类算法不应受ID影响
data_clean = data.drop(['CUST_ID'], axis=1)  # axis=1表示删除列

# ==================== 处理缺失值 ====================
# 使用前向填充方法处理缺失值,即用前一个有效值填充
data_clean = data_clean.ffill()  # ffill=forward fill,前向填充

print(f'\n缺失值处理:')
print(data_clean.isnull().sum().sum())  # 检查是否还有缺失值,应该输出0

# ==================== 数据描述统计 ====================
print('\n数据描述统计:')
print(data_clean.describe())  # 显示各特征的统计量(均值、标准差、分位数等)

32.4 K-Means聚类

任务要求:将下方代码原样输入教学平台并运行(注释可省略)。该代码读取信用卡用户数据,删除客户 ID 列并用前向填充处理缺失值后,以 K=5 对用户行为特征做 K-Means 聚类,输出聚类散点图、各簇特征均值表与平均轮廓系数。

列表 32.2: 平台原始代码
# 注:09_CC GENERAL.csv数据文件本地没有,但平台已经内置
# ⚠️ 平台原始代码 - 请原样输入至教学平台(注释除外),平台才会判定答案正确
import pandas as pd  # 导入Pandas数据分析库
data = pd.read_csv('09_CC GENERAL.csv')  # 从CSV文件读取数据存入data
 
# 数据处理
data.drop(['CUST_ID'], axis=1, inplace=True)
data.fillna(method='ffill', inplace=True)  # 使用前向填充法处理缺失值;旧版写法,新版 pandas 请改用 data = data.ffill()
 
from sklearn.cluster import KMeans  # 导入Scikit-learn的KMeans模块
import matplotlib.pyplot as plt  # 导入Matplotlib绘图库
 
# 使用Kmeans算法对数据进行聚类
kmeans = KMeans(n_clusters=5, n_init=10, max_iter=300,algorithm="elkan", random_state=42)
kmeans.fit(data)  # 在数据上训练kmeans模型
 
# 可视化展示聚类结果
plt.scatter(data.iloc[:, 0], data.iloc[:, 1], c=kmeans.labels_, cmap='viridis')
plt.savefig("s.png")  # 保存图形至文件
plt.show()  # 显示图形
 
# 分析不同类别的用户特征和行为差异
data['cluster'] = kmeans.labels_
cluster_summary = data.groupby('cluster').mean()  # 按指定列分组聚合
print(cluster_summary)  # 输出聚类数据
 
# 对聚类结果进行评估
from sklearn.metrics import silhouette_score
print(silhouette_score(data, kmeans.labels_))  # 输出平均轮廓系数

预期输出:平台先输出一张散点图(以数据的前两个特征维度为横纵坐标,按簇着色),随后打印 cluster_summary 各簇特征均值表与一个平均轮廓系数。判读要点:散点图只展示了前两维,高维簇结构未必在图上完全显现,不宜仅凭图形下结论;均值表中逐列比较各簇高低,找出区分度最大的特征(如余额、购买金额、现金提取行为),据此为每个簇命名客户画像;轮廓系数越接近 1 簇结构越好,接近 0 说明簇间重叠严重。09_CC GENERAL.csv 数据文件本地没有、平台已内置,具体数值以平台运行结果为准。

口径提示:平台代码计算轮廓系数时直接使用 silhouette_score(data, kmeans.labels_),而此时 data 已经新增了 cluster 标签列,该标签列被当作特征一并参与了距离计算;本章下方扩展代码则先用 data_clean.iloc[:, :-1] 剔除标签列再计算,两种口径得到的平均轮廓系数会有差异,严格评估聚类质量时应采用剔除标签列的做法。

32.5 聚类可视化

列表 32.3
# 注:该代码块依赖的数据来自上方平台任务代码块,本块承接上方平台任务的代码与数据(变量沿用平台任务)

# ==================== 可视化前两维特征的聚类结果 ====================
# 注意:高维数据只能投影到2D平面展示,可能丢失部分信息
plt.figure(figsize=(10, 8))  # 创建10x8英寸的画布

# 绘制散点图,不同簇用不同颜色表示
scatter = plt.scatter(
    data_clean.iloc[:, 0],  # x坐标:第一个特征
    data_clean.iloc[:, 1],  # y坐标:第二个特征
    c=labels,  # 颜色根据簇标签着色
    cmap='viridis',  # 使用viridis配色方案(紫色到黄色)
    s=50,  # 点的大小为50
    alpha=0.6  # 透明度0.6,便于看到重叠点
)

plt.colorbar(scatter, label='Cluster')  # 添加颜色条,显示簇标签
plt.title('信用卡用户聚类结果', fontsize=14)  # 设置标题
plt.xlabel('特征1', fontsize=12)  # x轴标签
plt.ylabel('特征2', fontsize=12)  # y轴标签
plt.grid(True, alpha=0.3)  # 添加网格线,透明度0.3
plt.tight_layout()  # 自动调整布局
plt.show()  # 显示图形

32.6 用户行为分析

列表 32.4: 不同簇用户行为特征分析
# 注:该代码块依赖的数据来自上方平台任务代码块,本块承接上方平台任务的代码与数据(变量沿用平台任务)

# ==================== 按簇统计特征均值 ====================
# 计算每个簇各特征的平均值,用于理解各簇的行为模式
cluster_summary = data_clean.groupby('cluster').mean()  # 按簇分组,计算均值

print('各簇平均特征:')
print(cluster_summary)
# 输出解读:比较各簇在余额、购买频率、信用额度等方面的差异

# ==================== 计算轮廓系数 ====================
# 轮廓系数衡量聚类质量:值越接近1,聚类效果越好
silhouette_avg = silhouette_score(data_clean.iloc[:, :-1], labels)  # 排除cluster列
print(f'\n平均轮廓系数: {silhouette_avg:.4f}')

# ==================== 轮廓系数解读 ====================
# 轮廓系数范围:[-1, 1]
# >0.5: 良好聚类,样本分配合理
# 0.2-0.5: 中等聚类,部分样本可能分配错误
# <0.2: 较差聚类,簇之间重叠严重
# <0: 样本可能被分配到错误的簇

print('\n轮廓系数解读:')
if silhouette_avg > 0.5:
    print('  聚类效果良好,簇间分离度高')
elif silhouette_avg > 0.2:
    print('  聚类效果中等,可考虑调整K值或特征')
else:
    print('  聚类效果较差,建议重新选择特征或算法')

32.7 商业应用

用户分群策略:

  1. 高消费高频率: VIP客户,专属服务
  2. 高消费低频率: 潜力客户,提升频率
  3. 低消费高频率: 价格敏感,优惠券激励
  4. 低消费低频率: 流失风险,召回活动

32.8 本章小结

要点:

  1. 聚类前必须做数据预处理:删除客户 ID 这类无关列、用前向填充等方法处理缺失值;
  2. 各簇的”画像”来自 groupby('cluster').mean() 的特征均值表,聚类价值最终体现在画像的业务解读上;
  3. 轮廓系数综合衡量簇内紧凑度与簇间分离度,取值 [-1, 1],是比肉眼读散点图更可靠的聚类质量指标;
  4. 客户细分结果应转化为差异化的营销与风控动作,而非止步于分出几个簇。

易错点:

  1. 忘记删除 ID 列,让无业务含义的编号参与距离计算,干扰聚类结果;
  2. 计算轮廓系数时把 cluster 标签列留在特征里(见”口径提示”),口径不当导致评估失真;
  3. 只看散点图前两维就断言聚类好坏,忽略高维信息在二维投影中的丢失;
  4. 把 K=5 当成唯一合理选择,不再检查其他 K 值或业务上是否真的需要五个客群。

32.9 动手与思考

以下练习每题附参考答案(默认折叠)。请先独立完成并写下你的判断,再点开对照,最后上机验证。

  1. 输出预测:某样本点 i 属于簇 A,其簇内平均距离 a(i)=2.0,到最近邻簇 B 的平均距离 b(i)=1.1。按轮廓系数公式 s=(b-a)/max(a,b) 计算 s(i) 并判断该点是否被分错了簇。

    参考答案(先写下你的预测再点开)

    解题思路:先代公式再判读。a(i)=2.0 是样本点 i 到本簇 A 其他点的平均距离,b(i)=1.1 是它到最近邻簇 B 各点的平均距离;分母取两者较大者 max(a,b)=2.0,故 s(i)=(1.1-2.0)/2.0=-0.9/2.0=-0.45。轮廓系数的取值范围是 [-1, 1]:s(i) 越接近 1 说明该点紧扣本簇、远离邻簇;接近 0 说明它落在两簇的边界上;小于 0 则说明它离邻簇比离本簇更近。本例 s(i)=-0.45<0,点 i 到簇 B 的平均距离(1.1)明显小于到本簇 A 的平均距离(2.0),它更像簇 B 的成员——在簇 A 待的位置上是被分错了的,K-Means 的“分配—更新”若能继续迭代,它正是最可能换簇(或形成换簇压力)的样本。

    # 验证脚本:代入轮廓系数公式计算s(i)并判断符号
    within_cluster_dist = 2.0  # 簇内平均距离a(i):到本簇A其他点的平均距离
    nearest_cluster_dist = 1.1  # 最近邻簇平均距离b(i):到邻簇B各点的平均距离
    silhouette_value = (nearest_cluster_dist - within_cluster_dist) / max(within_cluster_dist, nearest_cluster_dist)  # 公式s=(b-a)/max(a,b)
    print('s(i) =', silhouette_value)  # 输出轮廓系数
    print('是否小于0:', silhouette_value < 0)  # 小于0说明离邻簇更近,分错了簇

    预期输出(本机 peter 环境实际运行结果,具体以平台运行结果为准):

    s(i) = -0.44999999999999996
    是否小于0: True

    (注:-0.44999999999999996 即 -0.45,尾差是二进制浮点数无法精确表示 0.45 所致。)

    回扣本章:对应本章小结“要点”第 3 条——轮廓系数取值 [-1, 1],综合衡量簇内紧凑度与簇间分离度,负值提示样本可能被分到了错误的簇。

  2. 概念辨析:ffill() 前向填充与删除缺失值行两种处理方式各适合什么场景?对”余额”这类金额特征,前向填充可能引入什么偏差?

    参考答案(点开前请先独立完成)

    解题思路:两种方式各自的前提假设不同。删除缺失值行适合:样本量大、缺失行占比很低、且缺失与业务含义无关(随机缺失)的场景——损失少量样本换取“每一行都是真实观测”的干净口径。前向填充适合:时间序列中同一主体相邻时点的缺失(如某交易日无成交,用上一交易日数值递延),或缺失占比不容忽视、删除会明显缩水样本的场景。对“余额”这类金额特征,前向填充的偏差在于:它是把“前一个用户的余额”抄给缺失的用户(ffill 按行顺序取上一个有效值),而不同用户之间的余额本无可比性,这等于凭空制造了一个“ borrowing 来的观测”——若数据行恰好按余额高低或客群顺序排列,填充值会系统性偏大或偏小,扭曲该用户的画像并把误差传入距离计算与各簇均值表;即便行序随机,它也只是把噪声伪装成了数据。更稳妥的候选做法包括按列中位数/均值填充,或对比“删除行、填充、单独标记缺失”几种口径下聚类结果是否稳定。

    回扣本章:对应本章小结“要点”第 1 条(聚类前必须做数据预处理,删除无关列、处理缺失值)与“易错点”第 1 条——预处理口径本身会影响聚类结果,不能机械套用。

  3. 概念辨析:轮廓系数与簇内平方和 SSE 都能评价聚类,为什么簇数增多时 SSE 必然下降、轮廓系数却未必上升?这与第 章节 30 章的肘部法有何联系?

    参考答案(点开前请先独立完成)

    解题思路:根源在两个指标的构造方式不同。SSE 只看“簇内”:K 增大时每个簇被切得更小更紧,样本到本簇质心的距离只减不增,故 SSE 随 K 单调下降(极端情况 K 等于样本数时每个点自成一簇,SSE 为 0)——单调的指标本身没有“最优 K”的内部判据。轮廓系数同时看“簇内”与“簇间”:s(i)=(b(i)-a(i))/max(a(i),b(i)),其中 a(i) 是簇内平均距离、b(i) 是到最近邻簇的平均距离;K 增大时 a(i) 固然变小,但邻簇也变得更近,b(i) 随之下降,若切分穿过了自然的过渡带,边界样本的 b(i) 会逼近甚至小于 a(i),s(i) 反而变小——因此轮廓系数随 K 呈先升后降的形态,可在中等 K 处取到最大值,具备“选 K”的能力。与肘部法的联系:第 章节 30 章的肘部法正是为了弥补 SSE 的单调性缺陷,转而寻找“SSE 下降骤缓的拐点”;轮廓系数则从另一个方向补偿——引入簇间分离度,直接找 s 最大的 K。两者是互补的参考准则,实务中常并列报告、交叉验证,再接受业务合理性检验。

    回扣本章:对应本章小结“要点”第 3 条(轮廓系数综合衡量簇内紧凑度与簇间分离度)与“易错点”第 4 条(不把某个 K 当成唯一合理选择)。

  4. 变式任务:把平台代码中的 n_clusters 依次改为 3、4、6(其余参数不变),在平台上分别运行并记录平均轮廓系数,绘制”K-轮廓系数”对照表;结合各簇画像,你认为几个客群最便于制定差异化营销策略?

    参考答案(点开前请先独立完成)

    解题思路:改造思路——每次只改 n_clusters 一个参数(3、4、6 依次代入),n_init=10max_iter=300algorithm="elkan"random_state=42 等其余设置保持不变,这样轮廓系数的差异才能归因于 K 本身;每次运行后记录 silhouette_score 的输出,汇成“K—平均轮廓系数”对照表,并同步保存各 K 下的 cluster_summary 均值表。注意口径:平台代码把 cluster 标签列留在了特征里再算轮廓系数(见本章“口径提示”),严格评估时应先用 data_clean.iloc[:, :-1] 剔除标签列再算,自己复算时全程保持同一口径。结构性判读——轮廓系数随 K 不一定单调:K 调小时每个簇更大更杂,簇内距离变大;K 调大则可能切开自然的过渡带、边界样本变差,两种方向都可能压低得分,应观察哪个 K 附近得分相对较高。营销视角的选 K 标准不是“轮廓系数最高”一项,还要看各簇画像:便于制定差异化策略的 K 应使各簇在余额、购买金额、取现行为等特征上呈现清晰的“高/低”分化(每个簇都能说出一句话画像,如“高余额高消费”“低余额高频取现”),且簇数与可运营的客群资源匹配——通常 3—5 个客群足以支撑差异化的产品与触达方案,K 过大反而使策略碎片化。

    预期输出:数据文件由教学平台内置(09_CC GENERAL.csv 本地没有),本机无法复现,具体数值以平台运行结果为准。

    注意:以上为变式的改造思路与判读框架;列表 32.2 对应的平台原始代码块仍须原样输入教学平台,不要用本变式替换。

    回扣本章:对应本章小结“要点”第 4 条(客户细分应转化为差异化的营销与风控动作,而非止步于分出几个簇)与“易错点”第 2、4 条(口径统一、K 不是唯一答案)。